﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace BuddHa.Common.IpOpex
{
    public class IpAddressRange
    {
        public IPAddress Begin { get; set; }

        public IPAddress End { get; set; }

        public IpAddressRange()
        {
            Begin = new IPAddress(0L);
            End = new IPAddress(0L);
        }

        public IpAddressRange(string ipRangeString)
        {
            // remove all spaces.
            ipRangeString = ipRangeString.Replace(" ", "");

            // Pattern 1. CIDR range: "192.168.0.0/24", "fe80::/10"
            var m1 = Regex.Match(ipRangeString, @"^(?<adr>[\da-f\.:]+)/(?<maskLen>\d+)$", RegexOptions.IgnoreCase);
            if (m1.Success)
            {
                var adr = m1.Groups["adr"].Value;
                var baseAdrBytes = IPAddress.Parse(adr).GetAddressBytes();
                var maskLen = m1.Groups["maskLen"].Value;
                Begin = new IPAddress(baseAdrBytes);
                if (ipRangeString.Contains(":"))
                {
                    int index = adr.LastIndexOf(':');
                    End = IPAddress.Parse(adr.Substring(0, index + 1) + maskLen);
                }
                else
                {
                    int index = adr.LastIndexOf('.');
                    End = IPAddress.Parse(adr.Substring(0, index + 1) + maskLen);
                }
                return;
            }

            // Pattern 2. Uni address: "127.0.0.1", ":;1"
            var m2 = Regex.Match(ipRangeString, @"^(?<adr>[\da-f\.:]+)$", RegexOptions.IgnoreCase);
            if (m2.Success)
            {
                Begin = End = IPAddress.Parse(ipRangeString);
                return;
            }

            // Pattern 3. Begin end range: "169.258.0.0-169.258.0.255"
            var m3 = Regex.Match(ipRangeString, @"^(?<begin>[\da-f\.:]+)-(?<end>[\da-f\.:]+)$", RegexOptions.IgnoreCase);
            if (m3.Success)
            {
                Begin = IPAddress.Parse(m3.Groups["begin"].Value);
                End = IPAddress.Parse(m3.Groups["end"].Value);
                return;
            }

            // Pattern 4. Bit mask range: "192.168.0.0/255.255.255.0"
            var m4 = Regex.Match(ipRangeString, @"^(?<begin>[\da-f\.:]+)/(?<end>[\da-f\.:]+)$", RegexOptions.IgnoreCase);
            if (m4.Success)
            {
                Begin = IPAddress.Parse(m4.Groups["begin"].Value);
                End = IPAddress.Parse(m4.Groups["end"].Value);
                return;
            }

            throw new FormatException("Unknown IP range string.");
        }

        public bool Contains(IPAddress ipaddress)
        {
            if (ipaddress.AddressFamily != Begin.AddressFamily) return false;
            var adrBytes = ipaddress.GetAddressBytes();
            return Bits.GE(Begin.GetAddressBytes(), adrBytes) && Bits.LE(End.GetAddressBytes(), adrBytes);
        }
        internal static class Bits
        {
            internal static byte[] Not(byte[] bytes)
            {
                return bytes.Select(b => (byte)~b).ToArray();
            }

            internal static byte[] And(byte[] A, byte[] B)
            {
                return A.Zip(B, (a, b) => (byte)(a & b)).ToArray();
            }

            internal static byte[] Or(byte[] A, byte[] B)
            {
                return A.Zip(B, (a, b) => (byte)(a | b)).ToArray();
            }

            internal static bool GE(byte[] A, byte[] B)
            {
                return A.Zip(B, (a, b) => a == b ? 0 : a < b ? 1 : -1)
                    .SkipWhile(c => c == 0)
                    .FirstOrDefault() >= 0;
            }

            internal static bool LE(byte[] A, byte[] B)
            {
                return A.Zip(B, (a, b) => a == b ? 0 : a < b ? 1 : -1)
                    .SkipWhile(c => c == 0)
                    .FirstOrDefault() <= 0;
            }

            internal static byte[] GetBitMask(int sizeOfBuff, int bitLen)
            {
                var maskBytes = new byte[sizeOfBuff];
                var bytesLen = bitLen / 8;
                var bitsLen = bitLen % 8;
                for (var i = 0; i < bytesLen; i++)
                {
                    maskBytes[i] = 0xff;
                }
                if (bitsLen > 0) maskBytes[bytesLen] = (byte)~Enumerable.Range(1, 8 - bitsLen).Select(n => 1 << n - 1).Aggregate((a, b) => a | b);
                return maskBytes;
            }
        }
    }
}
